﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;

using Bling.Core;
using Bling.WPF;
using Bling.WPF.Util;
using Bling.Slides;
using Bling.Util;
using Bling.Shaders;
using Bling.Graphics;
namespace Bling.Example {
  using Bling.Matrices;
  using Bling.WPF3D;
  using Bling.DX;
  public class ParametricWithInference : HasHost {
    private static readonly Uri AutumnUri = "Resources/Autumn.jpg".MakePackUri(typeof(ParametricWithInference));
    // These three lines are boiler plate to start and stop the rendering loop as this canvas is active/inactive.
    public ParametricWithInference() : base(false) {
      // set host size and position
      Host.Size = 500d;
      Host.LeftTop = 0d;
      // first, create some sliders to control the simulation.
      LabelSliderBl Phase = new LabelSliderBl(this) {
        LabelName = "Phase",
        ZIndex = 100,
        LeftTop = Host.LeftBottom,
      };
      LabelSliderBl Frequency = new LabelSliderBl(this) {
        LabelName = "Frequency",
        Minimum = 0,
        Maximum = 40d,
        Value = 10d,
        ZIndex = 100,
        LeftTop = Phase.LeftBottom,
      };
      LabelSliderBl Magnitude = new LabelSliderBl(this) {
        LabelName = "Magnitude",
        Minimum = 0,
        Maximum = 0.5, Value = 0.05,
        ZIndex = 100,
        LeftTop = Frequency.LeftBottom,
      };
      var RotateEye = new LabelSliderBl(this) {
        LeftTop = Magnitude.LeftBottom,
        LabelName = "Eye",
        Minimum = 0,
        Maximum = 360,
        Value = 0
      };
      var RotateWorld = new LabelSliderBl(this) {
        LeftTop = RotateEye.LeftBottom,
        LabelName = "World",
        Minimum = 0,
        Maximum = 360,
        Value = 0
      };

      // world matrix specifies where a render object will go in the world.
      MatrixBl<D4, D4> World;
      // If we wanted to rotate the object in the world
      World = RotateWorld.Value.ToDegrees().AsRadians.RotateAround(new Point3DBl(1d, 1d, 0d)).Matrix;
      //  world = world * sliderY.Value.PI2().RotateAround(new Point3DBl(sliderZ, 1d, 1d));

      // represents where our eye is in the world.
      Point3DBl Center = new Point3DBl(0, 0, 0);
      //Point3DBl Eye = Center + new Point3DBl((rotateX0.Value + 90).ToDegrees().ToPoint.X * moveZ.Value, 0, (rotateX0.Value + 90).ToDegrees().ToPoint.Y * moveZ.Value);     

      // the 3D cordinate system:
      //  Y
      //  |   Z
      //  |  /
      //  | /
      //  |/
      // O -------- X
      // Configure a WPF perspective camera.
      PerspectiveCameraCl Camera = new PerspectiveCameraCl() {
        Position = Center + 
          ((RotateEye.Value + 90).ToDegrees().ToPoint * -4d).InsertY(0),
        Direction = { Look = Center }, FieldOfView = .25.PI().AsDegrees,
      };

      // Create a sphere displaced by an egg crate height field. 
      // The egg crate's frequency, magnitude, and phase are controlled
      // by WPF sliders.
      PSurface Surface = GraphicDefinitions.Sphere.Displace(GraphicDefinitions.EggCrate.Adjust(Frequency, Magnitude, Phase.Value * .25));


      // create a new device in which to hold rendering information
      // Create a texture from the Autumn resource Uri.
      TextureBl Autumn = new ImageTexture<D2>(AutumnUri);

      // This code defines how Surface is rendered. It takes a function that provides
      // the vertiex index (n), surface's parametric coordinate (uv), and a vertex result
      // structure (vertex).
      int DIM = 100;
      Device.Render(Surface, DIM, DIM, (IntBl n, PointBl uv, IVertex vertex) => {
        // First, specify where the vertex will appear on the screen. The parametric coordinate (uv)
        // allows us to access the vertex's relative position, which we multiple by the world, view,
        // and project matrices.
        //uv = uv.PerVertex();

        vertex.Position = (Camera.Project * Camera.View * World * Surface)[uv]; 
        // Compute normal for lighting. Parametric surface normals are computed
        // via symbolic differentiation. Only transform by the world matrix.
        // Must cast to point 3D because lighting computations doesn't work on 4D points,
        // casting will incorporate the w component into the resulting 3D point. 
        Point3DBl normal = ((Point3DBl)(World * Surface.Normals[uv])).Normalize;
        // Compute position for lighting. Again only transform by world matrix.
        Point3DBl usePosition = (Point3DBl)(Surface[uv] * World);

        // Create lighting by addition that we want to use on the color.
        DiffuseMaterialCl diffMaterial = new DiffuseMaterialCl() { 
          Ambient = { Factor = 1 }, 
          Factor = 1,
        };
        SpecularMaterialCl specMaterial = new SpecularMaterialCl() { Power = 3, Factor = 1, };
        // Light definitions borrowed from WPF 3D.
        AmbientLightBl ambLight = new AmbientLightBl() {
          Color = new ColorBl(0.5, 0.5, 0.5),
        };
        SpotLightBl spotLight = new SpotLightBl() {
          Direction = (new Point3DBl(-1, 0, -1)).Normalize,
          Color = new ColorBl(0.5, 0, 0),
          Position = new Point3DBl(5, 0, 5),
          InnerConeAngle = 2.ToDegrees(),
          OuterConeAngle = 4.ToDegrees(),
        };
        DirectionalLightBl dirLight = new DirectionalLightBl() {
          Direction = (new Point3DBl(-1, 0, -1)).Normalize,
          Color = new ColorBl(00, 0.5, 0.5),
          //Color = new ColorBl(1, 1, 1),
        };
        Lighting L = (diffMaterial + specMaterial).ApplyLights(ambLight + spotLight + dirLight);
        // Apply lighting, assign vertex color.
        // sample the Autumn texture using the current parametric point. The range of uv
        // is [-.5,-.5] to [+.5,+.5] while the range of a texture sample is [0,0] to [1,1].
        // As a result, we add a half a unit (0.5) to uv before sampling. 
        // Note that this computation will occur in the pixel shader where uv will be properly 
        // interpolated.
        vertex.Color = L.ApplyAt(usePosition, normal.PerPixel(), Camera.Position).ForPixel(Autumn, uv + 0.5);
      });
    }
  }
}
